package org.datavis.chart;

import java.text.NumberFormat;
import org.datavis.R;
import org.datavis.axis.HorizontalAxis;
import org.datavis.axis.VerticalAxis;
import org.datavis.data.Data;
import org.datavis.renderer.ChartRenderer;
import org.datavis.util.ChartUtil;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/**
 * @author Benjamin Vogler
 * @version 1.0
 * 
 * abstract 
 * Chart is a widget to for showing charts in an application
 */
public abstract class Chart extends LinearLayout {
	
	//constants
	public static final int ANGLED = 0;
	public static final int  STAGGERED = 1;
	public static final int STRAIGHT = 2;
	public static final int NOZOOM = 0;
	public static final int  ZOOMIN = 1;
	public static final int ZOOMOUT = 2;
	
	//data
	protected Data data = null;
	
	//layout
	protected LinearLayout topLayout;
	protected LinearLayout bottomLayout;
	protected VerticalAxis verticalAxis;
	protected HorizontalAxis horizontalAxis;
	
	//rendering
	protected ChartRenderer chartRenderer;
	
	//layout attributes
	protected String chartTitle = "";
	protected boolean drawOrigin = true;
	protected float fontSize = 11.f; //standard font size
	protected Typeface fontType = Typeface.DEFAULT; //standard fontType
	
	//vertical axis layout attributes
	protected boolean drawVAxis = true;
	protected String vAxisTitle = "";
	protected boolean drawVDescription = true;
	protected int vAxisWidth = 0;
	protected float vAxisWidthInPercent = 0.f;
	protected boolean tickMarks = true;
	protected double tickMarkRange = 0.;
	
	//horizontal axis layout attributes
	protected boolean drawHAxis = true;
	protected String hAxisTitle = "";
	protected boolean drawHDescription = true;
	protected int hAxisHeight = 0;
	protected float hAxisHeightInPercent = 0.f;
	protected int hAxisDescModus = ANGLED;
	protected int zoomModus = NOZOOM;
	
	protected float lineWidth = 1.0f;
	
	
	public Chart(Context context, AttributeSet attrs) {
		super(context, attrs);
		initializeAttributes(attrs);		
	}
	
	public Chart(Context context) {
		super(context);		
	}	
	 
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int _height;
		int _width;
	    _height = MeasureSpec.getSize(heightMeasureSpec);
	    _width = MeasureSpec.getSize(widthMeasureSpec);
	 
	    setMeasuredDimension(_width, _height);
	}	
	
	@Override
	protected void onLayout (boolean changed, int left, int top, int right, int bottom)
	{
		//super.onLayout (changed, left, top, right, bottom);
		setChildrenLayouts(changed, left, top, right, bottom);
	}	

	/**
	 * Initializes the custom attributes defined in /res/attr.xml and set in xml element
	 * @param attrs custom AttributSet of xml element
	 */
	protected void initializeAttributes(AttributeSet attrs) {
		
		TypedArray a = getContext().obtainStyledAttributes(attrs,
			    R.styleable.Chart);
			 
		final int N = a.getIndexCount();
		for (int i = 0; i < N; ++i)
		{
		    int attr = a.getIndex(i);
		    switch (attr)
		    {
		        case R.styleable.Chart_chartTitle:
		        	chartTitle = a.getString(attr);
		            break;
		        case R.styleable.Chart_drawOrigin:
		        	drawOrigin = a.getBoolean(attr, true);
		            break;
		        case R.styleable.Chart_drawVAxis:
		        	drawVAxis = a.getBoolean(attr, true);
		            break;
		        case R.styleable.Chart_vAxisTitle:
		        	vAxisTitle = a.getString(attr);
		            break;
		        case R.styleable.Chart_drawVDescription:
		        	drawVDescription = a.getBoolean(attr, true);
		            break;
		        case R.styleable.Chart_vAxisWidth:
		        	vAxisWidth = a.getInteger(attr, 0);
		            break;
		        case R.styleable.Chart_vAxisWidthInPercent:
		        	vAxisWidthInPercent = a.getFloat(attr, 20.f);
		            break;
		        case R.styleable.Chart_tickMarks:
		        	tickMarks = a.getBoolean(attr, true);
		            break;
		        case R.styleable.Chart_tickMarkRange:
		        	tickMarkRange = a.getFloat(attr, 0.f);
		            break;
		        case R.styleable.Chart_drawHAxis:
		        	drawHAxis = a.getBoolean(attr, true);
		            break;
		        case R.styleable.Chart_hAxisTitle:
		        	hAxisTitle = a.getString(attr);
		            break;
		        case R.styleable.Chart_drawHDescription:
		        	drawHDescription = a.getBoolean(attr, true);
		            break;
		        case R.styleable.Chart_hAxisHeight:
		        	hAxisHeight = a.getInteger(attr, 0);
		            break;
		        case R.styleable.Chart_hAxisHeightInPercent:
		        	hAxisHeightInPercent = a.getFloat(attr, 20.f);
		            break;		       
		        case R.styleable.Chart_hAxisDescModus:
		        	hAxisDescModus = a.getInteger(attr,0);
		            break;
		        case R.styleable.Chart_zoomModus:
		        	zoomModus = a.getInteger(attr,0);
		            break;
		        case R.styleable.Chart_fontSize:
		        	fontSize = a.getFloat(attr, 11.f);
		            break;
		        case R.styleable.Chart_fontType:
		        	fontType = Typeface.createFromAsset(getContext().getAssets(), a.getString(attr));
		            break;
		        case R.styleable.Chart_lineWidth:
		            lineWidth = a.getFloat(attr, 1.0f);
		    }
		}
		a.recycle();
	}	
	
	/**
	 * Measures and sets the of all child elements layout in the available space.
	 * Depends on if horizontal and or vertical axis are enabled (default is enabled) in element attributes.
	 * 
	 * @param changed
	 * @param left
	 * @param top
	 * @param right
	 * @param bottom
	 */
	protected void setChildrenLayouts(boolean changed, int left, int top, int right, int bottom)
	{
		int width = this.getWidth();
		int height = this.getHeight();
		int leftWidth = width;
		int topHeight = height;
		int bottomHeight = height;
		
		//if vertical axis exists
		if(drawVAxis)
    	{
			if(vAxisWidth != 0)
			{
				leftWidth = vAxisWidth;
			}
			else if(vAxisWidthInPercent != 0.f)
			{
				leftWidth = (int) (width * (vAxisWidthInPercent/100));
			}
			else //standard value range between 30 and 100 and 20%
			{
				leftWidth = this.caculateVAxisWidth(fontSize);
			}
    	}
		    	
		//if horizontal axis exists
    	if(drawHAxis)
    	{
    		if(hAxisHeight != 0)
			{
				bottomHeight = hAxisHeight;
				topHeight = height - bottomHeight;
			}
			else if(hAxisHeightInPercent != 0.f)
			{
				bottomHeight = (int) (height * (hAxisHeightInPercent/100));
				topHeight = height - bottomHeight;
			}
			else //standard value range between 30 and 100 and 20%
			{
				bottomHeight = caculateHAxisHeight(fontSize);
				topHeight = height - bottomHeight;
			}   		
    	}
    	
    	//set children layouts
    	if(drawVAxis && drawHAxis)
    	{    		
    		topLayout.layout(0, 0, width, topHeight);
        	bottomLayout.layout(0, topHeight, width, height);
        	verticalAxis.layout(0, 0, leftWidth, topHeight);
        	chartRenderer.layout(leftWidth, 0, width, topHeight);
        	horizontalAxis.layout(0, 0, width, bottomHeight);
        	horizontalAxis.setSpacer(leftWidth);
        }
    	else if (drawVAxis)
    	{
    		verticalAxis.layout(0, 0, leftWidth, height);
        	chartRenderer.layout(leftWidth, 0, width, height);
    	}
    	else if (drawHAxis)
    	{
    		chartRenderer.layout(0, 0, width, topHeight);
        	horizontalAxis.layout(0, topHeight, width, height);     	
    	}
    	else
    	{
    		chartRenderer.layout(0, 0, width, height);
    	}
 	}
	
	/**
	 * Initializes the Layout
	 */
	protected void initLayout()
	{		
		if(drawVAxis && drawHAxis)
    	{    		
			initSubLayout();
			initVerticalAxis();
    		initChartRenderer();    		
    		initHorizontalAxis();
        }
    	else if (drawVAxis)
    	{
    		initVerticalAxis();
    		initChartRenderer();  
    	}
    	else if (drawHAxis)
    	{
    		initChartRenderer();    		
    		initHorizontalAxis();     	
    	}
    	else
    	{
    		initChartRenderer(); 
    	}
		
	}
	
	
	/**
	 * initializes sub layout if horizontal axis is present
	 */
	protected void initSubLayout()
	{
		topLayout = new LinearLayout(getContext());
		topLayout.setOrientation(LinearLayout.HORIZONTAL);		
		topLayout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		this.addView(topLayout);				
		
		bottomLayout = new LinearLayout(getContext());		
		bottomLayout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		this.addView(bottomLayout);
	}
	
	/**
	 * initializes vertical axis
	 */
	protected void initVerticalAxis()
	{
		verticalAxis = new VerticalAxis(getContext(), this);
		verticalAxis.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));		
		if(drawVAxis && drawHAxis)
			topLayout.addView(verticalAxis);
		else
			this.addView(verticalAxis);
	}
	
	/**
	 * initializes horizontal axis
	 */
	protected void initHorizontalAxis()
	{
		horizontalAxis.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		if(drawVAxis && drawHAxis)
			bottomLayout.addView(horizontalAxis);
		else
			this.addView(horizontalAxis);
	}
	
	/**
	 * initializes chart renderer
	 */
	protected void initChartRenderer()
	{
		chartRenderer.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));		
		if(drawVAxis && drawHAxis)
			topLayout.addView(chartRenderer, 0);
		else
			this.addView(chartRenderer, 0);
	}
	
	/**
	 * calculates horizontal axis height based on font size
	 * @param fontSize font size 
	 * @return horizontal axis height in pixel
	 */
	protected int caculateHAxisHeight(float fontSize)
	{
		int width = 0;
		Paint mPaint = new Paint();
		mPaint.setTextSize(fontSize);
		mPaint.setTypeface(fontType);
		
		if(this.drawHDescription)
		{
			if(hAxisDescModus == Chart.ANGLED)
			{
				float max = 0;
				float cur = 0;
				for(String item:data.getDescriptions())
				{
					cur = mPaint.measureText(item);
					cur = (float)((Math.pow(2,0.5)/2) * cur);
					max = Math.max(cur, max);
				}
				width = (int)max + (int)fontSize;
			}
			else if(hAxisDescModus == Chart.STAGGERED)
			{
				width = (int)(3 *(int)fontSize);
			}
			else if(hAxisDescModus == Chart.STRAIGHT)
			{
				width = (int)(1.5 *(int)fontSize);
			}
		}
				
		if(this.hAxisTitle != "")
		{
			width = width + (int)fontSize + 5;
		}
		
		return width;
	}
	
	/**
	 * calculates vertical axis width based on font size
	 * @param fontSize font size 
	 * @return vertical axis width in pixel
	 */
	protected int caculateVAxisWidth(float fontSize)
	{
		int width = 0;
		Paint mPaint = new Paint();
		mPaint.setTextSize(fontSize);
		mPaint.setTypeface(fontType);		
		
		if(this.tickMarks)
		{

//			float max = 0;
//			float cur = 0;
//			int tickTextlength = 0;
//			
//			ArrayList<ArrayList<Double>> tmp = data.getValues();
//			for (ArrayList<Double> item: tmp)
//			{
//				for(double it:item)
//				{
//					tickTextlength = ChartUtil.calculateDecimals(it);
//					if(tickTextlength > 0)
//					{
//						numberFormatter.setMaximumFractionDigits(0);
//					}
//					else
//					{
//						numberFormatter.setMaximumFractionDigits(1-tickTextlength);
//					}
//					cur = mPaint.measureText(numberFormatter.format(it));
//					max = Math.max(cur, max);
//				}
//			}
			
			String vRangeString = "";
			double vRange = data.getValueRange();
			NumberFormat numberFormatter = null;
			numberFormatter = NumberFormat.getInstance();
			
			if(data.getValueRange() > 1)
			{
				int vRangeInt = (int)(vRange);
				vRangeString = numberFormatter.format(vRangeInt);
			}
			else
			{
				
				int tickTextlength = ChartUtil.calculateDecimals(vRange);
				numberFormatter.setMaximumFractionDigits(1-tickTextlength);
				vRangeString = numberFormatter.format(vRange);
			}
			
			width = (int)mPaint.measureText(vRangeString) + (int)fontSize;
		}
				
		if(this.vAxisTitle != "")
		{
			width = width + (int)fontSize + 5;
		}
		
		return width;
	}
	
	/**
	 * refreshes the layout and redraws the chart 
	 */
	public void refreshChart(){
		this.requestLayout();
		//this.forceLayout();
		this.invalidate();
	}
		
	/**
	 * sets chart data and refreshes the layout
	 * @param data chart data
	 */
	public void setData(Data data){
		
		if(this.data == null)
		{
			this.removeAllViews();
			initLayout();
		}
		
		this.data = data;
		refreshChart();
		
	}
	
	public Data getData() {
		return data;
	}

	public ChartRenderer getChartRenderer() {
		return chartRenderer;
	}

	public boolean isDrawOrigin() {
		return drawOrigin;
	}

	public String getvAxisTitle() {
		return vAxisTitle;
	}

	public boolean isDrawVDescription() {
		return drawVDescription;
	}
	

	public boolean isTickMarks() {
		return tickMarks;
	}

	public double getTickMarkRange() {
		return tickMarkRange;
	}

	public String gethAxisTitle() {
		return hAxisTitle;
	}

		public float getFontSize() {
		return fontSize;
	}
	
	public void setFontSize(float fontSize) {
		this.fontSize = fontSize;
	}
	
	public boolean isDrawHDescription() {
		return drawHDescription;
	}
	
	public void setDrawHDescription(boolean drawHDescription) {
		this.drawHDescription = drawHDescription;
	}


	public int gethAxisDescModus() {
		return hAxisDescModus;
	}


	public void sethAxisDescModus(int hAxisDescModus) {
		this.hAxisDescModus = hAxisDescModus;
		this.postInvalidate();
	}


	public int getZoomModus() {
		return zoomModus;
	}


	public void setZoomModus(int zoomModus) {
		this.zoomModus = zoomModus;
	}

	public Typeface getFontType() {
		return fontType;
	}

	public void setFontType(Typeface fontType) {
		this.fontType = fontType;
	}

	public boolean isDrawVAxis() {
		return drawVAxis;
	}

	public void setDrawVAxis(boolean drawVAxis) {
		this.drawVAxis = drawVAxis;
	}

	public boolean isDrawHAxis() {
		return drawHAxis;
	}

	public void setDrawHAxis(boolean drawHAxis) {
		this.drawHAxis = drawHAxis;
	}
	
	public float getLineWidth() {
		return lineWidth;
	}

	public void setLineWidth(float lineWidth) {
		this.lineWidth = lineWidth;
	}
}
